﻿/*                              
   Copyright 2011 Nils Kopal, Christopher Krüger, Simone Sauer, Dennis Nolte, Uni Duisburg-Essen

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Xml.Serialization;
using Sharebert.DownloadLibrary.Interfaces;
using System.IO;

namespace Sharebert.DownloadLibrary
{
    /// <summary>
    /// A DownloadListener waits for download requests by DownloadClients
    /// </summary>
    public class DownloadListener : IDownloadListener
    {
        #region Members

        private readonly IPAddress _listenIp;
        private int _listenPort;
        private TcpListener _listener;
        private Thread _listenThread;
        private bool _running;
        private int _timeout = 30000;
        private volatile int _downloaders;
        internal ArrayList Clients = ArrayList.Synchronized(new ArrayList());
        #endregion

        #region Constrcutors

        /// <summary>
        /// Construct a download listener listening for file requests on the given listenOp and listenPort
        /// </summary>
        /// <param name="listenIp"></param>
        /// <param name="listenPort"></param>
        public DownloadListener(IPAddress listenIp, int listenPort)
        {
            if (listenIp == null)
            {
                throw new ArgumentNullException("listenIp");
            }
            if (listenPort < 0 || listenPort > 65535)
            {
                throw new ArgumentException("listenPort");
            }
            _listenIp = listenIp;
            _listenPort = listenPort;
        }

        #endregion

        #region IDownloadListener Members

        /// <summary>
        /// Start listening for download requests
        /// </summary>
        public void Start()
        {
            if (_running)
            {
                throw new InvalidOperationException("DownloadListener already running!");
            }
            _running = true;
            _listener = new TcpListener(_listenIp, _listenPort);
            _listenThread = new Thread(new ThreadStart(ListenForClients));
            _listenThread.Name = "ListenThread";
            _listenThread.Start();                        
        }

        /// <summary>
        /// Stop listening for download requests / stop handling actual downloads
        /// </summary>
        public void Stop()
        {
            _listener.Stop();
            _running = false;
            foreach(TcpClient client in new ArrayList(Clients))
            {
                if(client.Connected)
                {
                    try
                    {
                        client.Close();
                    }
                    catch(Exception)
                    {
                        //wtf?
                    }
                    Clients.Remove(client);
                }
            }
        }

        /// <summary>
        /// Is this DownloadListener currently listening for download requests or not
        /// </summary>
        /// <returns></returns>
        public bool IsRunning()
        {
            return _running;
        }

        /// <summary>
        /// The read/write timeout for the network communication
        /// </summary>
        public int Timeout { get { return _timeout; } set { _timeout = value; } }

        /// <summary>
        /// handle incoming client requests
        /// </summary>
        private void ListenForClients()
        {
            try{
                _listener.Start(10);
                _listenPort = ((IPEndPoint)_listener.Server.LocalEndPoint).Port;                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
                _running = false;
                if(ErrorOccured != null)
                {
                    var args = new ErrorArgs();
                    args.Exception = ex;
                    ErrorOccured.Invoke(this,args);
                }
                return;
            }           
            while (_running)
            {
                try
                {
                    var client = _listener.AcceptTcpClient();
                    client.SendTimeout = Timeout;
                    client.ReceiveTimeout = Timeout;
                    var clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
                    clientThread.Name = "ClientThread";
                    clientThread.Start(client);
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.StackTrace);
                }
            }
        }

        /// <summary>
        /// Handle a single client (wait for a file request)
        /// </summary>
        /// <param name="client"></param>
        private void HandleClient(object client)
        {
            TcpClient tcpClient = null;
            try
            {
                tcpClient = (TcpClient)client;
                Clients.Add(tcpClient);
                NetworkStream networkStream = tcpClient.GetStream();
                networkStream.ReadTimeout = Timeout;
                Message message = Message.ReceiveMessage(networkStream);

                if(message.Header.MessageType == MessageType.FileRequestMessage)
                {
                    var serializer = new XmlSerializer(typeof(RequestMessage));
                    var stream = new MemoryStream(message.Payload);
                    var requestMessage = (RequestMessage)serializer.Deserialize(stream);

                    if(FileRequestReceived != null)
                    {
                        var args = new FileRequestEventArgs();
                        args.Filename = requestMessage.Filename;
                        args.Filesize = requestMessage.Filesize;
                        args.DownloadClientHandler = new DownloadClientHandler(tcpClient,this);
                        FileRequestReceived.Invoke(this, args);
                    }
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
                _running = false;
                if (ErrorOccured != null)
                {
                    var args = new ErrorArgs();
                    args.Exception = ex;
                    ErrorOccured.Invoke(this, args);
                }
            }
        }

        /// <summary>
        /// Increase the count of the amount of active downloaders and
        /// fire DownloadersChanged event
        /// </summary>
        internal void IncreaseDownloaders()
        {
            _downloaders++;
            if(DownloadersChanged != null)
            {
                var args = new DownloadersArgs();
                args.ActiveDownloads = _downloaders;
                DownloadersChanged.Invoke(this,args);
            }
        }

        /// <summary>
        /// Decrease the count of the amount of active downloaders and
        /// fire DownloadersChanged event
        /// </summary>
        internal void DecreaseDownloaders()
        {
            _downloaders--;
            if (DownloadersChanged != null)
            {
                var args = new DownloadersArgs();
                args.ActiveDownloads = _downloaders;
                DownloadersChanged.Invoke(this, args);
            }
        }       

        /// <summary>
        /// Fired if a new file request occurs
        /// </summary>
        public event EventHandler<FileRequestEventArgs> FileRequestReceived;

        /// <summary>
        /// Fired if an error occured
        /// </summary>
        public event EventHandler<ErrorArgs> ErrorOccured;

        /// <summary>
        /// Fired each time a download starts or stops and contains the actual number of downloads
        /// </summary>
        public event EventHandler<DownloadersArgs> DownloadersChanged;

        #endregion

        #region IDownloadListener Members


        public int ListenPort
        {
            get { return _listenPort; }
        }

        #endregion

        public string ListenIp { get; set; }
    }

}
